﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml.Data;

namespace System.Collections.Generic
{
    public class IncrementalLoadingObservableCollection<T> : ObservableCollection<T>, ISupportIncrementalLoading
    {
        private IDataListSource<T> m_dataListSource;

        private bool isLoading;
        public bool IsLoading
        {
            get
            {
                return isLoading;
            }
            protected set
            {
                if (isLoading != value)
                {
                    isLoading = value;
                    base.OnPropertyChanged(new PropertyChangedEventArgs("IsLoading"));
                }
            }
        }

        public IncrementalLoadingObservableCollection()
        {
        }

        public IncrementalLoadingObservableCollection(IDataListSource<T> dataListSource)
            : base()
        {
            m_dataListSource = dataListSource;
        }
        public IncrementalLoadingObservableCollection(IDataListSource<T> dataListSource, IEnumerable<T> list)
            : base(list)
        {
            m_dataListSource = dataListSource;
        }


        private int minimumPagingSize=5;
        public int MinimumPagingSize
        {
            get
            {
                return minimumPagingSize;
            }
            set
            {
                if (minimumPagingSize != value)
                {
                    minimumPagingSize = value;
                    OnPropertyChanged(new PropertyChangedEventArgs("MinimumPagingSize"));
                }
            }
        }
        public int? sourceCount;

        private async Task<LoadMoreItemsResult> LoadMoreItemsAsyncInternal(int count)
        {
            IsLoading = true;

            // If we currently do not know the number of items then fetch this

            if (sourceCount == null)
                sourceCount = await m_dataListSource.GetCountAsync();

            // Set a minimum paging size if requested

            count = Math.Max(count, MinimumPagingSize);

            // Limit the number of items to fetch to the number of remaining items

            count = Math.Min(count, sourceCount.Value - this.Count);

            // Get all the items and wait until they are all fetched

            Task<T>[] fetchItemTasks = new Task<T>[count];
            int startIndex = this.Count;

            for (int i = 0; i < count; i++)
                fetchItemTasks[i] = m_dataListSource.GetItemAsync(startIndex + i);

            await Task.WhenAll(fetchItemTasks);

            fetchItemTasks.ForEach(task => this.Add(task.Result));

            // Increment the current count

            //currentCount += count;

            // Set properties and raise property changed for HasMoreItems if this is not false

            IsLoading = false;

            //if (currentCount == sourceCount)
            //    OnPropertyChanged("HasMoreItems");

            // Raise collection changed events

            //OnItemsAdded(startIndex, count);

            // Return the number of items added

            return new LoadMoreItemsResult() { Count = (uint)count };
        }

        public bool HasMoreItems
        {
            get
            {
                // If we have not yet retrived the number of items from the data list source then return true
                if (sourceCount == null)
                    return true;

                // Otherwise return true only if there are items yet to be retrived
                else
                    return this.Count < sourceCount;
            }
        }

        public global::Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            return LoadMoreItemsAsyncInternal((int)count).AsAsyncOperation();
        }
    }

    public class GroupInfo<TKey, TElement> :
        ObservableCollection<TElement>,
        IGrouping<TKey, TElement>
    {
        public TKey Key { get; set; }

        public GroupInfo()
        {
        }

        public new ObservableCollection<TElement> Items
        {
            get
            {
                return this;
            }
        }

        public GroupInfo(IGrouping<TKey, TElement> o)
        {
            this.Key = o.Key;

            foreach (var i in o)
                this.Add(i);
        }

        //public IEnumerator<TElement> GetEnumerator()
        //{
        //    return (this.Items ?? Enumerable.Empty<TElement>()).GetEnumerator();
        //}

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this.Items ?? Enumerable.Empty<TElement>()).GetEnumerator();
        }
    }


    public class GrouppedObservableCollection<TKey, TElement> : ObservableCollection<GroupInfo<TKey, TElement>>, ISupportIncrementalLoading
    {
        private Func<TElement, TKey> _pred;
        ISupportIncrementalLoading ilsrc;
        //(from t in this group t by t.City into g orderby g.Key select g).ToList();
        public GrouppedObservableCollection(INotifyCollectionChanged src, Func<TElement, TKey> pred)
            : base()
        {
            _pred = pred;
            ilsrc = src as ISupportIncrementalLoading;
            src.CollectionChanged += OnSRCCollectionChanged;
        }

        void OnSRCCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    AddGrouppedItems(e.NewItems.OfType<TElement>());
                    break;
                case NotifyCollectionChangedAction.Remove:
                    RemoveGrouppedItems(e.NewItems.OfType<TElement>());
                    break;
                case NotifyCollectionChangedAction.Replace:
                    ReplaceGrouppedItems(e.OldItems.OfType<TElement>(), e.NewItems.OfType<TElement>());
                    break;
                case NotifyCollectionChangedAction.Move:
                    throw new NotImplementedException();
                    break;
                case NotifyCollectionChangedAction.Reset:
                    this.ClearItems();
                    break;
            }
        }
        private GroupInfo<TKey, TElement> FindOutGroupInfo(GroupInfo<TKey, TElement> gi)
        {
            GroupInfo<TKey, TElement> foundOut = (from i in this
                                                  let gil = i as GroupInfo<TKey, TElement>
                                                  where object.Equals(gil.Key, gi.Key)
                                                  select gil).FirstOrDefault();
            return foundOut;
        }

        private void AddGrouppedItems(IEnumerable<TElement> items)
        {
            var groupped = from t in items
                           let val = _pred(t)
                           group t by val into g
                           orderby g.Key
                           select new GroupInfo<TKey, TElement>(g);
            foreach (var gi in groupped)
            {
                var foundOut = FindOutGroupInfo(gi);
                if (foundOut != null)
                    foreach (var item in gi)
                        foundOut.Add((TElement)item);
                else
                {
                    this.Add(gi);
                }
            }
        }
        private void RemoveGrouppedItems(IEnumerable<TElement> items)
        {
            var groupped = from t in items
                           let val = _pred(t)
                           group t by val into g
                           orderby g.Key
                           select new GroupInfo<TKey, TElement>(g);
            foreach (var gi in groupped)
            {
                var foundOut = FindOutGroupInfo(gi);
                if (foundOut != null)
                    foreach (var item in gi)
                        foundOut.Remove((TElement)item);
            }
        }

        private void ReplaceGrouppedItems(IEnumerable<TElement> oldItems, IEnumerable<TElement> newItems)
        {
            RemoveGrouppedItems(oldItems);
            AddGrouppedItems(newItems);
        }

        public bool HasMoreItems
        {
            get {
                if (ilsrc == null)
                    return false;
                return ilsrc.HasMoreItems; 
            }
        }

        public global::Windows.Foundation.IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
        {
            if (ilsrc == null)
                return null;
            return ilsrc.LoadMoreItemsAsync(count);
        }
    }
}
